#include "projectile.h"

CProjectile::CProjectile(const string &szProjectileName, const ENTITY_ID &nId,
                         const float fX, const float fY, const float fZ) :
    CMovableEntity(szProjectileName, nId, fX, fY, fZ, true),
    m_bInMotion(false)
{
    /**
     * @param const string &szProjectileName The name of the projectile (also
     *  used to load a model)
     * @param const ENTITY_ID &nId The ID of the projectile
     * @param const float fX The X position of the projectile
     * @param const float fY The Y position of the projectile
     * @param const float fZ The Z position of the projectile
     */
    CMesh *pMesh(CMeshContainer::getInstance()->getMesh(szProjectileName));
    if (pMesh == nullptr)
    {
        pMesh = CMeshContainer::getInstance()->addMesh(szProjectileName);
        pMesh->loadMesh(CDeviceManager::getInstance()->getDevice(), 
            (string("models/") + szProjectileName + ".x").c_str());
    }

    CEntityContainer::getInstance()->addEntity(this);

    CDisplayable *pDisplayable(
        CDisplayableContainer::getInstance()->createDisplayable(pMesh));

    setDisplayable(pDisplayable);

    setVelocity(0.0f, 0.0f, 0.0f);

    CMap *pMap(CMapContainer::getInstance()->getCurrentMap());
    if (pMap != nullptr)
    {
        int nHeight(0), nWidth(0), nDepth(0);
        pMap->getTilePositionFromCoord(nHeight,
                    nWidth, nDepth, getX(),  getY(), getZ(), 0);
        if (pMap->isValidTile(nHeight, nWidth, nDepth))
        {
                
            pMap->getTile(nHeight, nWidth, nDepth)->addEntityInBlock(this);
        }
    }

    pDisplayable->setScale(2.0f, 2.0f, 2.0f);
}

CProjectile::CProjectile(const string &szProjectileName, const string &szMesh,
                const ENTITY_ID &nId, const float fX, const float fY, 
                const float fZ) :
    CMovableEntity(szProjectileName, nId, fX, fY, fZ, true),
    m_bInMotion(false)
{
    string szMeshName(string("models/") + szMesh + ".x");
    CMesh *pMesh(CMeshContainer::getInstance()->getMesh("tile"));
    if (pMesh == nullptr)
    {
        pMesh = CMeshContainer::getInstance()->addMesh("tile");
        pMesh->loadMesh(CDeviceManager::getInstance()->getDevice(), 
            szMeshName.c_str());
    }

    CEntityContainer::getInstance()->addEntity(this);

    CDisplayable *pDisplayable(
        CDisplayableContainer::getInstance()->createDisplayable(pMesh));

    setDisplayable(pDisplayable);

    CMap *pMap(CMapContainer::getInstance()->getCurrentMap());
    if (pMap != nullptr)
    {
        int nHeight(0), nWidth(0), nDepth(0);
        pMap->getTilePositionFromCoord(nHeight,
                    nWidth, nDepth, getX(),  getY(), getZ(), 0);
        if (pMap->isValidTile(nHeight, nWidth, nDepth))
        {
            pMap->getTile(nHeight, nWidth, nDepth)->addEntityInBlock(this);
        }
    }
}

CProjectile::~CProjectile()
{
    /**
     * Destructor, ensure that we remove this entity from any valid tile.
     */
    int nHeight(0), nWidth(0), nDepth(0);
    CMap *pMap(CMapContainer::getInstance()->getCurrentMap());
    if (pMap != nullptr)
    {
        pMap->getTilePositionFromCoord(nHeight, nWidth, nDepth, getX(), getY(),
            getZ(), 0);
        if (pMap->isValidTile(nHeight, nWidth, nDepth))
        {
            CTile *pTile(pMap->getTile(nHeight, nWidth, nDepth));
            if (pTile != nullptr)
            {
                pTile->removeEntityFromBlock(this);
            }
        }
    }
}

D3DXVECTOR3 CProjectile::getVelocity() const
{
    /**
     * @return D3DXVECTOR3 The velocity of the projectile
     */
    return m_vecVelocity;
}

void CProjectile::setVelocity(D3DXVECTOR3 vecVelocity)
{
    /**
     * @param D3DXVECTOR3 vecVelocity The velocity if the projectile
     */
    m_vecVelocity = vecVelocity;
}

void CProjectile::setVelocity(float fX, float fY, float fZ)
{
    /**
     * @param float fX The X velocity value.
     * @param float fY The Y velocity value.
     * @param float fZ The Z velocity value.
     */
    setXVelocity(fX);
    setYVelocity(fY);
    setZVelocity(fZ);
}

float CProjectile::getXVelocity() const
{
    /**
     * @return float The X velocity of the projectile
     */
    return m_vecVelocity.x;
}

float CProjectile::getYVelocity() const
{
    /**
     * @return float The Y velocity of the projectile
     */
    return m_vecVelocity.y;
}

float CProjectile::getZVelocity() const
{
    /**
     * @return float The Z velocity of the projectile
     */
    return m_vecVelocity.z;
}

void CProjectile::setXVelocity(float fX)
{
    /**
     * @param float fX The new X velocity of the projectile
     */
    m_vecVelocity.x = fX;
}

void CProjectile::setYVelocity(float fY)
{
    /**
     * @param float fY The new Y velocity of the projectile
     */
    m_vecVelocity.y = fY;
}

void CProjectile::setZVelocity(float fZ)
{
    /**
     * @param float fZ The new Z velocity of the projectile
     */
    m_vecVelocity.z = fZ;
}

void CProjectile::tick()
{
    /**
     * Executed every tick on this projectile. If it's animating, then apply the
     * velocities.
     */
    if (m_bInMotion)
    {
        CMapContainer *CMaps(CMapContainer::getInstance());
        if (! CMaps->isMapLoaded())
        {
            return;
        }

        CMap *pMap(CMaps->getCurrentMap());

        // The object is currently in motion, apply the motion and do collision
        // testing / gravity
        setYVelocity(getYVelocity() - 
            (static_cast<float>(m_LastAnimatedTime.getTimeDifference()) 
            / 1000.0f * Constants::GRAVITY));

        // Make sure -63 < 0 < 63 so object can never go more than 1 tile at
        // a time
        setYVelocity(min(getYVelocity(), Constants::TILE_SIZE - 0.1f));
        setYVelocity(max(getYVelocity(), -Constants::TILE_SIZE + 0.1f));

        TBoundingShape boundingObject(getDisplayable()->getBoundingShape());

        float xPos(getX() + Constants::TILE_SIZE / 2);
        float yPos(getY() + Constants::TILE_SIZE / 2 /*- 
                        (boundingObject.vecMaxBound.y - 
                        boundingObject.vecMinBound.y)*/);
        float zPos(getZ() + Constants::TILE_SIZE / 2);

        int nCurrentWidth = static_cast<int>(xPos / Constants::TILE_SIZE);
        int nCurrentDepth = static_cast<int>(zPos / Constants::TILE_SIZE);
        int nCurrentHeight = static_cast<int>(yPos / Constants::TILE_SIZE);

        int nNewWidth = static_cast<int>((xPos + getXVelocity()) 
            / Constants::TILE_SIZE);
        int nNewHeight = static_cast<int>((yPos + getYVelocity()) 
            / Constants::TILE_SIZE);
        int nNewDepth = static_cast<int>((zPos + getZVelocity()) 
            / Constants::TILE_SIZE);

        // Only interested in the new block
        nNewWidth = nCurrentWidth;
        nNewDepth = nCurrentDepth;
        nNewHeight = nCurrentHeight - 1;

        bool bCollision(false);

        if (nNewHeight < 0 || nNewWidth < 0 || nNewDepth < 0)
        {
            return;
        }

        if (nNewHeight < pMap->getHeight())
        {

            CTile *pNewTile(pMap->getTile(
                    nNewHeight, nNewWidth, nNewDepth));

            int nHeight2(0), nWidth2(0), nDepth2(0);
            int nHeight(0), nWidth(0), nDepth(0);
            pMap->getTilePositionFromCoord(nHeight2,
                nWidth2, nDepth2, getX() + getXVelocity(), 
                getY() + getYVelocity(), getZ() + getZVelocity(), 0);
            pMap->getTilePositionFromCoord(nHeight,
                nWidth, nDepth, getX(),  getY(), getZ(), 0);

            if (nHeight2 != nHeight ||
                nWidth2 != nWidth ||
                nDepth2 != nDepth)
            {
                
                if (pMap->isValidTile(nHeight, nWidth, nDepth))
                {
                    pMap->getTile(
                        nHeight, nWidth, nDepth)->removeEntityFromBlock(this);
                }
                if (pMap->isValidTile(nHeight2, nWidth2, nDepth2))
                {
                    CTile *pTile2(pMap->getTile(nHeight2, nWidth2, nDepth2));
                    pTile2->addEntityInBlock(this);
                }
            }

            if (!pNewTile->isWalkable())
            {
                        
                TBoundingShape otherBound(
                    pNewTile->getDisplayable()->getBoundingShape());
                    

                if (boundingObject.vecMinBound.y + getYVelocity() <
                    otherBound.vecMaxBound.y)
                {
                    // Collision on the floor
                    if (abs(getYVelocity()) < 2.5f)
                    {
                        // Stop the animation
                        setY(getY() + getYVelocity() +
                            (otherBound.vecMaxBound.y - 
                            boundingObject.vecMinBound.y + getYVelocity()) + 
                            5.0f);
                        setYVelocity(0.0f);
                        setInMotion(false);
                    }
                    else
                    {
                        // Invert the gravity of the object.
                        bCollision = true;
                    }
                }
            }
        }
        if (bCollision)
        {
            setYVelocity(getYVelocity() * -0.5f);
        }   
        setY(getY() + getYVelocity());
        m_LastAnimatedTime.setTime();
    }
}

void CProjectile::setInMotion(bool bInMotion)
{
    /**
     * Sets the projectile's motion to being active.
     *
     * @param bool bInMotion Whether or not to activate the motion
     */
    m_bInMotion = bInMotion;
    m_LastAnimatedTime.setTime();
}

bool CProjectile::getInMotion() const
{
    /**
     * @return bool Whether or not the projectile is currently active.
     */
    return m_bInMotion;
}